package com.hjk.common.aop.aspect;

import com.hjk.common.aop.annotation.Reties;
import com.hjk.common.utils.check.Check;
import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 方法执行失败重试
 *
 * @author huangjunkai
 * @version 1.0
 * @date 2020/6/3 9:33
 */
@Aspect
@Component
public class RetiesAspect {

    ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);

    private static final Logger logger = LoggerFactory.getLogger(RetiesAspect.class);

    @Pointcut("@annotation(com.hjk.common.aop.annotation.Reties)")
    private void reties() {

    }

    @Around("reties()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {


        String result = point.proceed().toString();

        //TODO 4.问题:因为是重试执行方法存在于内存中,服务器宕机后会停止重试:需持久化
        //TODO 5.入库字段 类.方法,参数,重试次数,第一次开始重试时间,间隔倍数,创建时间

        //消息
        Object[] args = point.getArgs();
        if (Check.isNotEmpty(args)) {
            //6.失败,则开启线程 进行方法重试
            if (Check.strIsNotBlank(result)) {
                if (RetiesConstant.ERROR.equals(result)) {
                    pool.execute(new RetiesTask(point));
                }
            }
        }

        return result;
    }


    /**
     * 获取方法上的注解
     *
     */
    private static Reties getAnnotation(JoinPoint joinPoint) {
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        String methodName = joinPoint.getSignature().getName();
        if (0 < methods.length) {
            for (Method met : methods) {
                Reties relog = met.getAnnotation(Reties.class);
                if (null != relog && methodName.equals(met.getName())) {
                    return relog;
                }
            }
        }
        return null;
    }


    @Data
    final static class RetiesTask implements Runnable {
        private ProceedingJoinPoint point;

        public RetiesTask(ProceedingJoinPoint point, String method, String paramJson) {
            this.point = point;

        }

        public RetiesTask(ProceedingJoinPoint point) {
            this.point = point;
        }

        public RetiesTask() {
        }

        @Override
        public void run() {

            //1.获取方法上注解@KafkaConsumerReties
            Reties annotation = getAnnotation(point);
            //2.获取注解参数 reties重试次数 initialInterval第一次开始重试时间 multiplier间隔倍数
            int reties = annotation.reties();
            long interval = annotation.initialInterval();
            int multiplier = annotation.multiplier();

            if (reties <= 0) {
                reties = RetiesConstant.RETIES;
            }
            if (interval < 0) {
                interval = RetiesConstant.INITIAL_INTERVAL;
            }

            if (multiplier < 0) {
                multiplier = RetiesConstant.MULTIPLIER;
            }

            //全限定方法名
            String fully = point.getTarget().getClass().getName() + "." + point.getSignature().getName();
            //第一个参数为 消息
            String paramJson = Optional.of(point.getArgs()[0]).orElse("").toString();

            logger.info("========方法 [ [ {} ] ] 重试中========参数为: {}", fully, paramJson);
            try {
                for (int i = 0; i < reties; i++) {
                    //方法执行间隔时间
                    TimeUnit.SECONDS.sleep(interval);

                    //执行重试`````````
                    String result = point.proceed().toString();
                    if (i == 1) {
                        result = RetiesConstant.SUCCESS;
                    }
                    if (RetiesConstant.SUCCESS.equals(result)) {
                        logger.info("========方法[ [ {} ] ]重试中成功========参数为: {}", fully, paramJson);
                        return;
                    }
                    //下次睡眠时间
                    interval = interval + interval * multiplier;
                }

            } catch (Throwable e) {
                logger.error("========<<<<<<方法[ [ {} ] ]重试失败>>>>>>>========", fully, e);
            }

        }


    }
}
